﻿using System;
using System.Collections.Generic;
using System.Data.Services;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Web;
using MugenInjection.Bindings.Builders;
using MugenInjection.Infrastructure;
using MugenInjection.Interface;
using MugenInjection.Interface.Components;
using MugenInjection.Parameters;
using MugenInjection.Syntax;
using MugenInjection.Wcf.Config;
using MugenInjection.Wcf.DataServiceHostImpl;
using MugenInjection.Wcf.Interface;
using MugenInjection.Wcf.ServiceHostImpl;
using MugenInjection.Wcf.WebServiceHostImpl;
using MugenInjection.Web;
using MugenInjection.Web.Interface;

// ReSharper disable CheckNamespace

namespace MugenInjection
// ReSharper restore CheckNamespace
{
    /// <summary>
    /// Fluent api for wcf configuration.
    /// </summary>
    public static class WcfConfigExtension
    {
        #region Parameters key

        public const string EndpointDispatcherParameterKey = "EndpointDispatcherParameterKey";

        public const string ServiceDescriptionParameterKey = "ServiceDescriptionParameterKey";

        public const string ServiceEndpointParameterKey = "ServiceEndpointParameterKey";

        #endregion

        #region Binding keys

        public const string InstanceProviderKey = "____________________InstanceProviderKey_________________";

        public const string ReleaseInstanceKey = "____________________ReleaseInstanceKey_________________";

        public const string ServiceBehaviorKey = "____________________ServiceBehaviorKey_________________";

        public const string MessageInspectorKey = "__________________MessageInspectorKey_________________";

        public const string EndpointBehaviorKey = "__________________EndpointBehaviorKey_________________";

        #endregion

        #region Public app config

        #region Application configuration extensions

        /// <summary>
        /// Creates specified <see cref="IApplicationConfiguration"/>. 
        /// Use this method if you not use <see cref="MugenInjectionHttpApplication"/>, with new <see cref="IInjector"/>.
        /// </summary>
        /// <param name="useDisposeBehavior">Indicates that will need use the DisposeReleaseInstanceBehavior.</param>
        /// <returns>An instance of <see cref="IWcfApplicationConfiguration"/>.</returns>
        public static IWcfApplicationConfiguration CreateWcfConfiguration(bool useDisposeBehavior = true)
        {
            return CreateWcfConfiguration(new MugenInjector(), useDisposeBehavior);
        }

        /// <summary>
        /// Creates specified <see cref="IApplicationConfiguration"/>. 
        /// Use this method if you not use <see cref="MugenInjectionHttpApplication"/>.
        /// </summary>
        /// <param name="injector">The specified <see cref="IInjector"/> for set in <see cref="MugenInjectionHttpApplication"/>.</param>
        /// <param name="useDisposeBehavior">Indicates that will need use the DisposeReleaseInstanceBehavior.</param>
        /// <returns>An instance of <see cref="IWcfApplicationConfiguration"/>.</returns>
        public static IWcfApplicationConfiguration CreateWcfConfiguration( IInjector injector,
                                                                          bool useDisposeBehavior = true)
        {
            Validate.ArgumentNotNull(injector, "injector");
            var applicationConfiguration = new WcfApplicationConfiguration(injector);
            MugenInjectionHttpApplication.ApplicationConfiguration = applicationConfiguration;
            return applicationConfiguration.WithWcfConfiguration(useDisposeBehavior);
        }

        /// <summary>
        /// Configures specified <see cref="IApplicationConfiguration"/> with WCF settings. Added support for ContextRequestScope.
        /// </summary>
        /// <param name="configuration">The specified <see cref="IApplicationConfiguration"/></param>
        /// <param name="useDisposeBehavior">Indicates that will need use the DisposeReleaseInstanceBehavior.</param>
        /// <returns>An instance of <see cref="IApplicationConfiguration"/>.</returns>
        public static T WithWcfConfiguration<T>( this T configuration, bool useDisposeBehavior = true)
            where T : IApplicationConfiguration
        {
            Validate.ArgumentNotNull(configuration, "configuration");
            configuration.AddConfiguration(new MugenWcfApplicationConfiguration());
            if (useDisposeBehavior)
                configuration.WithDisposeReleaseInstanceBehavior();
            return configuration;
        }

        /// <summary>
        /// Configures specified <see cref="IApplicationConfiguration"/> with specified <see cref="IReleaseInstanceBehavior"/>.
        /// </summary>
        /// <param name="configuration">The specified <see cref="IApplicationConfiguration"/></param>
        /// <param name="releaseInstanceBehaviorSyntax">The specified <see cref="IReleaseInstanceBehavior"/> syntax.</param>
        /// <param name="removeOld">Indicates that will need remove all old bindings.</param>
        /// <returns>An instance of <see cref="IApplicationConfiguration"/>.</returns>
        public static T WithReleaseInstanceBehavior<T>( this T configuration,
                                                        Func
                                                           <IGenericBindingSyntax<IReleaseInstanceBehavior>,
                                                           IBindingBuilder> releaseInstanceBehaviorSyntax,
                                                       bool removeOld = true)
            where T : IApplicationConfiguration
        {
            Validate.ArgumentNotNull(configuration, "configuration");
            Validate.ArgumentNotNull(releaseInstanceBehaviorSyntax, "releaseInstanceBehaviorSyntax");
            if (removeOld)
                configuration.Injector.Unbind<IReleaseInstanceBehavior>(ReleaseInstanceKey);
            IBindingBuilder behaviorSyntax =
                releaseInstanceBehaviorSyntax(configuration.Injector.BindWithManualBuild<IReleaseInstanceBehavior>());
            behaviorSyntax.Settings[BindingBuilderConfigurator.KeyedBindingKey] = ReleaseInstanceKey;
            behaviorSyntax.Build();
            return configuration;
        }

        /// <summary>
        /// Configures specified <see cref="IApplicationConfiguration"/> with DisposeReleaseInstanceBehavior.
        /// </summary>
        /// <param name="configuration">The specified <see cref="IApplicationConfiguration"/></param>
        /// <param name="removeOld">Indicates that will need remove all old bindings.</param>
        /// <returns>An instance of <see cref="IApplicationConfiguration"/>.</returns>
        public static T WithDisposeReleaseInstanceBehavior<T>( this T configuration, bool removeOld = true)
            where T : IApplicationConfiguration
        {
            Validate.ArgumentNotNull(configuration, "configuration");
            configuration.WithReleaseInstanceBehavior(syntax => syntax.ToConstant(new DisposeReleaseInstanceBehavior()));
            return configuration;
        }

        /// <summary>
        /// Configures specified <see cref="IApplicationConfiguration"/> with specified <see cref="IDispatchMessageInspector"/>.
        /// </summary>
        /// <param name="configuration">The specified <see cref="IApplicationConfiguration"/></param>
        /// <param name="dispatchMessageInspectorSyntax">The specified <see cref="IDispatchMessageInspector"/> syntax.</param>
        /// <returns>An instance of <see cref="IApplicationConfiguration"/>.</returns>
        public static T WithDispatchMessageInspector<T>( this T configuration,
                                                         Func
                                                            <IGenericBindingSyntax<IDispatchMessageInspector>,
                                                            IBindingBuilder> dispatchMessageInspectorSyntax)
            where T : IApplicationConfiguration
        {
            Validate.ArgumentNotNull(configuration, "configuration");
            Validate.ArgumentNotNull(dispatchMessageInspectorSyntax, "dispatchMessageInspectorSyntax");
            IBindingBuilder syntax =
                dispatchMessageInspectorSyntax(configuration.Injector.BindWithManualBuild<IDispatchMessageInspector>());
            syntax.Settings[BindingBuilderConfigurator.KeyedBindingKey] = MessageInspectorKey;
            syntax.Build();
            return configuration;
        }

        /// <summary>
        /// Configures specified <see cref="IApplicationConfiguration"/> with specified <see cref="IServiceBehavior"/>.
        /// </summary>
        /// <param name="configuration">The specified <see cref="IApplicationConfiguration"/></param>
        /// <param name="serviceBehaviorSyntax">The specified <see cref="IServiceBehavior"/> syntax.</param>
        /// <returns>An instance of <see cref="IApplicationConfiguration"/>.</returns>
        public static T WithServiceBehavior<T>( this T configuration,
                                                Func<IGenericBindingSyntax<IServiceBehavior>, IBindingBuilder>
                                                   serviceBehaviorSyntax) where T : IApplicationConfiguration
        {
            Validate.ArgumentNotNull(configuration, "configuration");
            Validate.ArgumentNotNull(serviceBehaviorSyntax, "serviceBehaviorSyntax");
            IBindingBuilder syntax =
                serviceBehaviorSyntax(configuration.Injector.BindWithManualBuild<IServiceBehavior>());
            syntax.Settings[BindingBuilderConfigurator.KeyedBindingKey] = ServiceBehaviorKey;
            syntax.Build();
            return configuration;
        }

        /// <summary>
        /// Configures specified <see cref="IApplicationConfiguration"/> with specified <see cref="IServiceBehavior"/>.
        /// </summary>
        /// <param name="configuration">The specified <see cref="IApplicationConfiguration"/></param>
        /// <param name="endPointBehaviorSyntax">The specified <see cref="IServiceBehavior"/> syntax.</param>
        /// <returns>An instance of <see cref="IApplicationConfiguration"/>.</returns>
        public static T WithEndPointBehavior<T>( this T configuration,
                                                 Func<IGenericBindingSyntax<IEndpointBehavior>, IBindingBuilder>
                                                    endPointBehaviorSyntax) where T : IApplicationConfiguration
        {
            Validate.ArgumentNotNull(configuration, "configuration");
            Validate.ArgumentNotNull(endPointBehaviorSyntax, "endPointBehaviorSyntax");
            IBindingBuilder syntax =
                endPointBehaviorSyntax(configuration.Injector.BindWithManualBuild<IEndpointBehavior>());
            syntax.Settings[BindingBuilderConfigurator.KeyedBindingKey] = EndpointBehaviorKey;
            syntax.Build();
            return configuration;
        }

        #endregion

        #region Service host extensions

        /// <summary>
        /// Initializes a new instance of the <see cref="ServiceHost"/> class with the type of service and its base addresses specified.
        /// </summary>
        /// <param name="serviceType">The type of hosted service.</param><param name="baseAddresses">An array of type <see cref="T:System.Uri"/> that contains the base addresses for the hosted service.</param><exception cref="T:System.ArgumentNullException"><paramref name="serviceType"/> is null.</exception>
        /// <returns>An instance of <see cref="ServiceHost"/>.</returns>
        public static ServiceHost CreateServiceHost(Type serviceType, params Uri[] baseAddresses)
        {
            Validate.ArgumentNotNull(serviceType, "serviceType");
            return new MugenInjectionServiceHost(serviceType, baseAddresses);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ServiceHost"/> class with the instance of the service and its base addresses specified.
        /// </summary>
        /// <param name="singletonInstance">The instance of the hosted service.</param>
        ///<param name="baseAddresses">An array of type <see cref="T:System.Uri"/> that contains the base addresses for the hosted service.</param><exception cref="T:System.ArgumentNullException"><paramref name="singletonInstance"/> is null.</exception>
        /// <returns>An instance of <see cref="ServiceHost"/>.</returns>
        public static ServiceHost CreateServiceHost(object singletonInstance, params Uri[] baseAddresses)
        {
            Validate.ArgumentNotNull(singletonInstance, "singletonInstance");
            return new MugenInjectionServiceHost(singletonInstance, baseAddresses);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="WebServiceHost"/> class with the type of service and its base addresses specified.
        /// </summary>
        /// <param name="serviceType">The type of hosted service.</param><param name="baseAddresses">An array of type <see cref="T:System.Uri"/> that contains the base addresses for the hosted service.</param><exception cref="T:System.ArgumentNullException"><paramref name="serviceType"/> is null.</exception>
        /// <returns>An instance of <see cref="WebServiceHost"/>.</returns>
        public static WebServiceHost CreateWebServiceHost(Type serviceType, params Uri[] baseAddresses)
        {
            Validate.ArgumentNotNull(serviceType, "serviceType");
            return new MugenInjectionWebServiceHost(serviceType, baseAddresses);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="WebServiceHost"/> class with the instance of the service and its base addresses specified.
        /// </summary>
        /// <param name="singletonInstance">The instance of the hosted service.</param>
        ///<param name="baseAddresses">An array of type <see cref="T:System.Uri"/> that contains the base addresses for the hosted service.</param><exception cref="T:System.ArgumentNullException"><paramref name="singletonInstance"/> is null.</exception>
        /// <returns>An instance of <see cref="WebServiceHost"/>.</returns>
        public static WebServiceHost CreateWebServiceHost(object singletonInstance, params Uri[] baseAddresses)
        {
            Validate.ArgumentNotNull(singletonInstance, "singletonInstance");
            return new MugenInjectionWebServiceHost(singletonInstance, baseAddresses);
        }

        /// <summary>
        /// Instantiates <see cref="DataServiceHost"/> for an ADO.NET Data Service.
        /// </summary>
        /// <param name="serviceType">Identifies the ADO.NET Data Service to the host.</param><param name="baseAddresses">The URI of the host.</param>
        /// <returns>An instance of <see cref="DataServiceHost"/>.</returns>
        public static DataServiceHost CreateDataServiceHost(Type serviceType, Uri[] baseAddresses)
        {
            Validate.ArgumentNotNull(serviceType, "serviceType");
            return new MugenInjectionDataServiceHost(serviceType, baseAddresses);
        }

        #endregion

        #endregion

        #region Internal

        /// <summary>
        /// Determines whether the given service is a InstanceContextMode.Single service.
        /// </summary>
#if DEBUG
        public static bool IsServiceSingleton(Type service)
#else
        internal static bool IsServiceSingleton(Type service)
#endif

        {
            var serviceBehavior = InjectorUtilsInternal.GetAttribute<ServiceBehaviorAttribute>(service);
            return serviceBehavior != null && serviceBehavior.InstanceContextMode == InstanceContextMode.Single;
        }

        internal static void InitializeServiceHostOnInitializeRuntime(ServiceHostBase serviceHostBase)
        {
            IInjector injector = MugenInjectionHttpApplication.ApplicationConfiguration.Injector;
            var behaviorManagerComponent = injector.Components.Get<IBehaviorManagerComponent>();
            IEnumerable<EndpointDispatcher> endpointDispatchers = serviceHostBase
                .ChannelDispatchers
                .OfType<ChannelDispatcher>()
                .SelectMany(channelDispatcher => channelDispatcher.Endpoints);
            foreach (EndpointDispatcher endpointDispatcher in endpointDispatchers)
            {
                var dict = new Dictionary<string, object> { { EndpointDispatcherParameterKey, endpointDispatcher } };
                //Sets an InstanceProvider.
                IInstanceProvider instanceProvider;
                if (!injector.TryGet(out instanceProvider, InstanceProviderKey, dict,
                                     new ConstructorParameter("serviceType", serviceHostBase.Description.ServiceType)))
                    throw new InvalidOperationException("The main container is not registered IInstanceProvider.");
                endpointDispatcher.DispatchRuntime.InstanceProvider = instanceProvider;

                //Adding an internal inspectors.
                IList<IDispatchMessageInspector> dispatchMessageInspectors =
                    behaviorManagerComponent.GetAll<IDispatchMessageInspector>();
                foreach (IDispatchMessageInspector dispatchMessageInspector in dispatchMessageInspectors)
                    endpointDispatcher.DispatchRuntime.MessageInspectors.Add(dispatchMessageInspector);

                //Adding an user inspectors.
                IEnumerable<IDispatchMessageInspector> result;
                if (!injector.TryGetAllInto(out result, MessageInspectorKey, serviceHostBase.Description.ServiceType, null, dict))
                    continue;
                foreach (IDispatchMessageInspector dispatchMessageInspector in result)
                    endpointDispatcher.DispatchRuntime.MessageInspectors.Add(dispatchMessageInspector);
            }
        }

        internal static void InitializeServiceHostOnOpening(ServiceHostBase serviceHostBase)
        {
            IInjector injector = MugenInjectionHttpApplication.ApplicationConfiguration.Injector;
            var dict = new Dictionary<string, object> { { ServiceDescriptionParameterKey, serviceHostBase.Description } };
            //Adding an user IServiceBehavior
            IEnumerable<IServiceBehavior> serviceBehaviors;
            if (injector.TryGetAllInto(out serviceBehaviors, ServiceBehaviorKey, serviceHostBase.Description.ServiceType, null, dict))
            {
                foreach (IServiceBehavior serviceBehavior in serviceBehaviors)
                    serviceHostBase.Description.Behaviors.Add(serviceBehavior);
            }

            //Adding an user IEndpointBehavior
            foreach (ServiceEndpoint endpoint in serviceHostBase.Description.Endpoints)
            {
                dict = new Dictionary<string, object> { { ServiceEndpointParameterKey, endpoint } };
                IEnumerable<IEndpointBehavior> endpointBehaviors;
                if (!injector.TryGetAllInto(out endpointBehaviors, EndpointBehaviorKey, serviceHostBase.Description.ServiceType, null, dict))
                    continue;
                foreach (IEndpointBehavior endpointBehavior in endpointBehaviors)
                    endpoint.Behaviors.Add(endpointBehavior);
            }
        }

        internal static void InitializeServiceHostOnCreate(Type serviceType, Uri[] baseAddresses,
                                                           Action<object, UriSchemeKeyedCollection> initializeSingleton,
                                                           Action<Type, UriSchemeKeyedCollection> initializeType)
        {
            var uriSchemeKeyedCollection = new UriSchemeKeyedCollection(baseAddresses);
            if (IsServiceSingleton(serviceType))
            {
                initializeSingleton(MugenInjectionHttpApplication.ApplicationConfiguration.Injector.Get(serviceType),
                                    uriSchemeKeyedCollection);
                return;
            }
            initializeType(serviceType, uriSchemeKeyedCollection);
        }

        #endregion
    }
}